const chokidar = require('chokidar')
const bodyParser = require('body-parser')
const chalk = require('chalk')
const path = require('path')

const mockDir = path.join(process.cwd(), 'mock')

/**
 * 在v4.0版本之后，在本地会启动一个mock-server来模拟数据，线上环境还是继续使用mockjs
 * 来进行模拟(因为本项目是一个纯前端项目，你也可以自己搭建一个线上server 来提供数据)。不
 * 管是本地还是线上所以的数据模拟都是基于mockjs生成的，所以只要写一套 mock 数据，就可以
 * 在多环境中使用。该方案的好处是，在保留 mockjs的优势的同时，解决之前的痛点。由于我们
 * 的 mock 是完全基于webpack-dev-serve来实现的，所以在你启动前端服务的同时，mock-server
 * 就会自动启动，这里还通过 chokidar 来观察 mock 文件夹内容的变化。在发生变化时会清除之前
 * 注册的mock-api接口，重新动态挂载新的接口，从而支持热更新。有兴趣的可以自己看一下代码
 *  mock-server.js。由于是一个真正的server，所以你可以通过控制台中的network，清楚的知道
 * 接口返回的数据结构。并且同时解决了之前mockjs会重写 XMLHttpRequest对象，导致很多第三方
 * 库失效的问题。在本地开发环境中基于webpack-dev-serve的 after这个middleware中间件，在这
 * 里自动读取你的mock文件，模拟出 REST API，它最大的好处是，完全不需要什么额外的工作，完全
 * 基于webpack-dev-serve就能实现。如果你还是想单独启动一个serve也是可以的，完全可以引入一个
 * express或者其它插件来启动一个 mock-serve。  
 *  */
function registerRoutes(app) {
  let mockLastIndex
  const { default: mocks } = require('./index.js')
  for (const mock of mocks) {
    app[mock.type](mock.url, mock.response)
    mockLastIndex = app._router.stack.length
  }
  const mockRoutesLength = Object.keys(mocks).length
  return {
    mockRoutesLength: mockRoutesLength,
    mockStartIndex: mockLastIndex - mockRoutesLength
  }
}

function unregisterRoutes() {
  Object.keys(require.cache).forEach(i => {
    if (i.includes(mockDir)) {
      delete require.cache[require.resolve(i)]
    }
  })
}

module.exports = app => {
  // es6 polyfill
  require('@babel/register')

  // parse app.body
  // https://expressjs.com/en/4x/api.html#req.body
  app.use(bodyParser.json())
  app.use(bodyParser.urlencoded({
    extended: true
  }))

  const mockRoutes = registerRoutes(app)
  var mockRoutesLength = mockRoutes.mockRoutesLength
  var mockStartIndex = mockRoutes.mockStartIndex

  // watch files, hot reload mock server
  chokidar.watch(mockDir, {
    ignored: /mock-server/,
    ignoreInitial: true
  }).on('all', (event, path) => {
    if (event === 'change' || event === 'add') {
      try {
        // remove mock routes stack
        app._router.stack.splice(mockStartIndex, mockRoutesLength)

        // clear routes cache
        unregisterRoutes()

        const mockRoutes = registerRoutes(app)
        mockRoutesLength = mockRoutes.mockRoutesLength
        mockStartIndex = mockRoutes.mockStartIndex

        console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed  ${path}`))
      } catch (error) {
        console.log(chalk.redBright(error))
      }
    }
  })
}
